﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;

namespace Fadd.Logging
{
	/// <summary>
	/// Default log provider.
	/// </summary>
	public class LogProvider : ILogProvider
	{
		private readonly List<IFormatter> _formatters = new List<IFormatter>();
		private readonly List<ILogger> _loggers = new List<ILogger>();
		private readonly List<ITarget> _targets = new List<ITarget>();
        
		#region ILogProvider Members

		/// <summary>
		/// Get a logger with the specified name.
		/// </summary>
		/// <param name="name">Configured name</param>
		/// <returns><c>null</c> will never be returned, you can always safely use the
		/// returned logger. </returns>
		public ILogger GetLogger(string name)
		{
			foreach (ILogger logger in _loggers)
			{
				if (string.Compare(logger.Name, name, true) == 0)
					return logger;
			}

			return LogManager.NullLogger;
		}

		/// <summary>
		/// Get logger for current class.
		/// </summary>
		/// <returns><c>null</c> will never be returned, you can always safely use the
		/// returned logger.
		/// </returns>
		public ILogger GetCurrentClassLogger()
		{
			var frame = new StackFrame(2);
			Type classType = frame.GetMethod().ReflectedType;

			// Find all matchig configurations
			IList<ILogger> loggers = FindLoggers(classType);
			if (loggers.Count == 0)
			{
				Console.WriteLine("NullLogger for " + classType.FullName);
				return LogManager.NullLogger;
			}
			if (loggers.Count == 1)
				return loggers[0];

			MultiLogger logger = new MultiLogger(loggers);
			LogLevel minLevel = LogLevel.Fatal;
			foreach (var logger1 in loggers)
			{
				if (logger1.MinLevel < minLevel)
					minLevel = logger1.MinLevel;
			}

			logger.LoadConfiguration(new LoggerConfiguration{MinLevel = minLevel, Name = "MultiLogger"});
			return logger;
		}

		#endregion

		/// <exception cref="InvalidOperationException"><c>InvalidOperationException</c>.</exception>
		/// <exception cref="ConfigurationErrorsException">If configuration is incorrect.</exception>
		public void LoadConfiguration()
		{
			var config = (Configuration) ConfigurationManager.GetSection("Logging");
			LoadFormatters(config.Formatters);
			LoadTargets(config.Targets);
			LoadLoggers(config.Loggers);
		}

		private void LoadFormatters(IEnumerable<FormatterConfiguration> formatterConfigurations)
		{
			foreach (FormatterConfiguration formatter in formatterConfigurations)
			{
				var instance = (IFormatter) Activator.CreateInstance(formatter.Type);
				instance.LoadConfiguration(formatter);
				_formatters.Add(instance);
			}
		}

		/// <summary>
		/// Loads the targets.
		/// </summary>
		/// <param name="targetConfigurations">The target configurations.</param>
		/// <exception cref="InvalidOperationException">Configured formatter cannot be found.</exception>
		private void LoadTargets(IEnumerable<TargetConfiguration> targetConfigurations)
		{
			foreach (TargetConfiguration targetConfig in targetConfigurations)
			{
				IFormatter formatter = GetFormatter(targetConfig.FormatterName);
				if (formatter == null)
					throw new InvalidOperationException("Formatter could not be found.");

				var target = (ITarget) Activator.CreateInstance(targetConfig.TargetType, new object[]{formatter});
				target.LoadConfiguration(targetConfig);
				_targets.Add(target);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="loggerConfigurations"></param>
		/// <exception cref="ConfigurationErrorsException">Failed to find configured target.</exception>
		protected void LoadLoggers(IEnumerable<LoggerConfiguration> loggerConfigurations)
		{
			foreach (LoggerConfiguration loggerConfig in loggerConfigurations)
			{
				IList<ITarget> targets = new List<ITarget>();
				foreach (string targetName in loggerConfig.TargetNames)
				{
					ITarget target = GetTarget(targetName);
					if (target == null)
						throw new ConfigurationErrorsException("Failed to target '" + targetName + "'.");
					targets.Add(target);
				}
				loggerConfig.Targets = targets;

				ILogger logger = new Logger();
				logger.LoadConfiguration(loggerConfig);
				_loggers.Add(logger);
			}
		}

		private ITarget GetTarget(string name)
		{
			foreach (ITarget target in _targets)
			{
				if (target.Name == name)
					return target;
			}

			return null;
		}

		///<summary>
		///
		///</summary>
		///<param name="name"></param>
		///<returns></returns>
		protected virtual IFormatter GetFormatter(string name)
		{
			foreach (IFormatter formatter in _formatters)
			{
				if (formatter.Name == name)
					return formatter;
			}

			return null;
		}

		private IList<ILogger> FindLoggers(Type classType)
		{
			var loggers = new List<ILogger>();
			foreach (ILogger logger in _loggers)
			{
				if (logger.CanLog(classType.Namespace))
					loggers.Add(logger);
			}
			return loggers;
		}
	}
}